/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <thread>

#include <gtest/hwext/gtest-ext.h>
#include <gtest/hwext/gtest-tag.h>

#include "hidebug/hidebug.h"
#include "hidebug/hidebug_type.h"
#include "securec.h"
#include <sys/mman.h>

using namespace testing::ext;

namespace {
class HidebugTest : public ::testing::Test {
protected:
    void SetUp() override {}
    void TearDown() override
    {
        system("param set hiviewdfx.debugenv.hidebug_test 0");
        system("param set libc.hook_mode 0");
    }
};

static void* TestCustomMalloc(size_t size)
{
    HiDebug_MallocDispatch* original = (HiDebug_MallocDispatch*)OH_HiDebug_GetDefaultMallocDispatchTable();
    return original->malloc(size);
}
static void DesCustomMalloc(HiDebug_MallocDispatch* current)
{
    HiDebug_MallocDispatch* original = (HiDebug_MallocDispatch*)OH_HiDebug_GetDefaultMallocDispatchTable();
    original->free(current);
    OH_HiDebug_RestoreMallocDispatchTable();
}

static void TestCustomFree(void* ptr)
{
    HiDebug_MallocDispatch* original = (HiDebug_MallocDispatch*)OH_HiDebug_GetDefaultMallocDispatchTable();
    EXPECT_NE(ptr, nullptr);
    original->free(ptr);
}

static void* TestCustomMmap(void* addr, size_t len, int prot, int flags, int fd, off_t offset)
{
    HiDebug_MallocDispatch* original = (HiDebug_MallocDispatch*)OH_HiDebug_GetDefaultMallocDispatchTable();
    return original->mmap(addr, len, prot, flags, fd, offset);
}

static int TestCustomMunmap(void* addr, size_t len)
{
    HiDebug_MallocDispatch* original = (HiDebug_MallocDispatch*)OH_HiDebug_GetDefaultMallocDispatchTable();
    EXPECT_NE(addr, nullptr);
    return original->munmap(addr, len);
}

static void* TestCustomCalloc(size_t nmemb, size_t size)
{
    HiDebug_MallocDispatch* original = (HiDebug_MallocDispatch*)OH_HiDebug_GetDefaultMallocDispatchTable();
    return original->calloc(nmemb, size);
}

static void* TestCustomRealloc(void* ptr, size_t size)
{
    HiDebug_MallocDispatch* original = (HiDebug_MallocDispatch*)OH_HiDebug_GetDefaultMallocDispatchTable();
    return original->realloc(ptr, size);
}

/**
 * @tc.name: OH_HiDebug_GetAppCpuUsage1
 * @tc.desc: test OH_HiDebug_GetAppCpuUsage.get app cpu usage
 * @tc.type: FUNC
 */
HWTEST_F(HidebugTest, OH_HiDebug_GetAppCpuUsage1, TestSize.Level1)
{
    EXPECT_TRUE(OH_HiDebug_GetAppCpuUsage() >= 0);
}

/**
 * @tc.name: OH_HiDebug_GetAppThreadCpuUsage1
 * @tc.desc: test OH_HiDebug_GetAppThreadCpuUsage.get thread cpu usage of app
 * @tc.type: FUNC
 */
HWTEST_F(HidebugTest, OH_HiDebug_GetAppThreadCpuUsage1, TestSize.Level1)
{
    HiDebug_ThreadCpuUsagePtr threadCpuUsage = OH_HiDebug_GetAppThreadCpuUsage();
    HiDebug_ThreadCpuUsagePtr curThreadCpuUsage = threadCpuUsage;
    while (curThreadCpuUsage != nullptr) {
        curThreadCpuUsage = curThreadCpuUsage->next;
    }
    OH_HiDebug_FreeThreadCpuUsage(&threadCpuUsage);
    HiDebug_ThreadCpuUsagePtr threadCpuUsage1 = nullptr;
    OH_HiDebug_FreeThreadCpuUsage(&threadCpuUsage1);
    OH_HiDebug_FreeThreadCpuUsage(nullptr);
    EXPECT_TRUE(true);
}

/**
 * @tc.name: GetSystemCpuUsage
 * @tc.desc: test InitEnvironmentParam for libc.hook_mode param set wrong_proc
 * @tc.type: FUNC
 */
HWTEST_F(HidebugTest, GetSystemCpuUsage, TestSize.Level1)
{
    double systemCpuUsage = OH_HiDebug_GetSystemCpuUsage();
    ASSERT_GE(systemCpuUsage, 0);
    ASSERT_LE(systemCpuUsage, 1);
}

/**
 * @tc.name: GetAppMemoryLimit1
 * @tc.desc: test GetAppMemoryLimit1
 * @tc.type: FUNC
 */
HWTEST_F(HidebugTest, GetAppMemoryLimit1, TestSize.Level1)
{
    OH_HiDebug_GetAppMemoryLimit(nullptr);
    HiDebug_MemoryLimit memoryLimit;
    OH_HiDebug_GetAppMemoryLimit(&memoryLimit);
    ASSERT_GE(memoryLimit.rssLimit, 0);
    ASSERT_GE(memoryLimit.vssLimit, 0);
}

/**
 * @tc.name: OH_HiDebug_GetAppNativeMemInfo1
 * @tc.desc: test OH_HiDebug_GetAppNativeMemInfo. get application process memory info
 * @tc.type: FUNC
 */
HWTEST_F(HidebugTest, OH_HiDebug_GetAppNativeMemInfo1, TestSize.Level1)
{
    HiDebug_NativeMemInfo nativeMemInfo;
    OH_HiDebug_GetAppNativeMemInfo(&nativeMemInfo);
    ASSERT_TRUE(nativeMemInfo.pss >= 0);
    ASSERT_TRUE(nativeMemInfo.vss >= 0);
    ASSERT_TRUE(nativeMemInfo.rss >= 0);
    ASSERT_TRUE(nativeMemInfo.sharedDirty >= 0);
    ASSERT_TRUE(nativeMemInfo.privateDirty >= 0);
    ASSERT_TRUE(nativeMemInfo.sharedClean >= 0);
    ASSERT_TRUE(nativeMemInfo.privateClean >= 0);
}

/**
 * @tc.name: OH_HiDebug_GetAppNativeMemInfoWithCache1
 * @tc.desc: test OH_HiDebug_GetAppNativeMemInfoWithCache. get application process memory info
 * @tc.type: FUNC
 */
HWTEST_F(HidebugTest, OH_HiDebug_GetAppNativeMemInfoWithCache1, TestSize.Level1)
{
    HiDebug_NativeMemInfo nativeMemInfo;
    OH_HiDebug_GetAppNativeMemInfoWithCache(&nativeMemInfo, true);
    HiDebug_NativeMemInfo cachedNativeMemInfo;
    OH_HiDebug_GetAppNativeMemInfoWithCache(&cachedNativeMemInfo, false);
    ASSERT_EQ(nativeMemInfo.pss, cachedNativeMemInfo.pss);
    ASSERT_EQ(nativeMemInfo.vss, cachedNativeMemInfo.vss);
    ASSERT_EQ(nativeMemInfo.rss, cachedNativeMemInfo.rss);
    ASSERT_EQ(nativeMemInfo.sharedDirty, cachedNativeMemInfo.sharedDirty);
    ASSERT_EQ(nativeMemInfo.privateDirty, cachedNativeMemInfo.privateDirty);
    ASSERT_EQ(nativeMemInfo.sharedClean, cachedNativeMemInfo.sharedClean);
    ASSERT_EQ(nativeMemInfo.privateClean, cachedNativeMemInfo.privateClean);
}

/**
 * @tc.name: OH_HiDebug_GetSystemMemInfo1
 * @tc.desc: test OH_HiDebug_GetSystemMemInfo. get system memory info
 * @tc.type: FUNC
 */
HWTEST_F(HidebugTest, OH_HiDebug_GetSystemMemInfo1, TestSize.Level1)
{
    OH_HiDebug_GetSystemMemInfo(nullptr);
    HiDebug_SystemMemInfo systemMemInfo;
    OH_HiDebug_GetSystemMemInfo(&systemMemInfo);
    ASSERT_GE(systemMemInfo.totalMem, 0);
    ASSERT_GE(systemMemInfo.freeMem, 0);
    ASSERT_GE(systemMemInfo.availableMem, 0);
}

/**
 * @tc.name: OH_HiDebug_StartAppTraceCapture1
 * @tc.desc: test OH_HiDebug_StartAppTraceCapture. start app capture trace
 * @tc.type: FUNC
 */
HWTEST_F(HidebugTest, OH_HiDebug_StartAppTraceCapture1, TestSize.Level1)
{
    uint32_t fileLength = 256;
    char fileName[256] = {0};
    HiDebug_TraceFlag flag = HIDEBUG_TRACE_FLAG_MAIN_THREAD;
    uint64_t tags = HIDEBUG_TRACE_TAG_COMMON_LIBRARY;
    uint32_t limitSize = 1024 * 1024;
    const char* targetPath = "/data/storage/el2/log";
    auto captureResult = OH_HiDebug_StartAppTraceCapture(flag, tags, limitSize, fileName, fileLength);
    if (std::filesystem::exists(targetPath)) {
        EXPECT_EQ(captureResult, HIDEBUG_SUCCESS);
        EXPECT_GT(sizeof(fileName) / sizeof(fileName[0]), 1);
        EXPECT_EQ(OH_HiDebug_StopAppTraceCapture(), HIDEBUG_SUCCESS);
    } else {
        EXPECT_EQ(captureResult, HIDEBUG_NO_PERMISSION);
        EXPECT_EQ(OH_HiDebug_StopAppTraceCapture(), HIDEBUG_NO_TRACE_RUNNING);
    }
}

/**
 * @tc.name: OH_HiDebug_StartAppTraceCapture2
 * @tc.desc: test OH_HiDebug_StartAppTraceCapture. repeat start app capture trace
 * @tc.type: FUNC
 */
HWTEST_F(HidebugTest, OH_HiDebug_StartAppTraceCapture2, TestSize.Level1)
{
    uint32_t fileLength = 256;
    char fileName[256] = {0};
    HiDebug_TraceFlag flag = HIDEBUG_TRACE_FLAG_MAIN_THREAD;
    uint64_t tags = HIDEBUG_TRACE_TAG_COMMON_LIBRARY;
    uint32_t limitSize = 1024 * 1024;
    const char* targetPath = "/data/storage/el2/log";
    auto captureResult = OH_HiDebug_StartAppTraceCapture(flag, tags, limitSize, fileName, fileLength);
    if (std::filesystem::exists(targetPath)) {
        EXPECT_EQ(captureResult, HIDEBUG_SUCCESS);
        auto captureResult2 = OH_HiDebug_StartAppTraceCapture(flag, tags, limitSize, fileName, fileLength);
        EXPECT_EQ(captureResult2, HIDEBUG_TRACE_CAPTURED_ALREADY);
        EXPECT_GT(sizeof(fileName) / sizeof(fileName[0]), 1);
        EXPECT_EQ(OH_HiDebug_StopAppTraceCapture(), HIDEBUG_SUCCESS);
    } else {
        EXPECT_EQ(captureResult, HIDEBUG_NO_PERMISSION);
        EXPECT_EQ(OH_HiDebug_StopAppTraceCapture(), HIDEBUG_NO_TRACE_RUNNING);
    }
}

/**
 * @tc.name: OH_HiDebug_StartAppTraceCapture3
 * @tc.desc: test OH_HiDebug_StartAppTraceCapture. repeat stop app capture trace
 * @tc.type: FUNC
 */
HWTEST_F(HidebugTest, OH_HiDebug_StartAppTraceCapture3, TestSize.Level1)
{
    uint32_t fileLength = 256;
    char fileName[256] = {0};
    HiDebug_TraceFlag flag = HIDEBUG_TRACE_FLAG_MAIN_THREAD;
    uint64_t tags = HIDEBUG_TRACE_TAG_COMMON_LIBRARY;
    uint32_t limitSize = 1024 * 1024;
    const char* targetPath = "/data/storage/el2/log";
    auto captureResult = OH_HiDebug_StartAppTraceCapture(flag, tags, limitSize, fileName, fileLength);
    if (std::filesystem::exists(targetPath)) {
        EXPECT_EQ(captureResult, HIDEBUG_SUCCESS);
        EXPECT_GT(sizeof(fileName) / sizeof(fileName[0]), 1);
        EXPECT_EQ(OH_HiDebug_StopAppTraceCapture(), HIDEBUG_SUCCESS);
        EXPECT_EQ(OH_HiDebug_StopAppTraceCapture(), HIDEBUG_NO_TRACE_RUNNING);
    } else {
        EXPECT_EQ(captureResult, HIDEBUG_NO_PERMISSION);
        EXPECT_EQ(OH_HiDebug_StopAppTraceCapture(), HIDEBUG_NO_TRACE_RUNNING);
    }
}

/**
 * @tc.name: OH_HiDebug_GetGraphicsMemory
 * @tc.desc: test OH_HiDebug_GetGraphicsMemory. get graphics memory.
 * @tc.type: FUNC
 */
HWTEST_F(HidebugTest, OH_HiDebug_GetGraphicsMemory, TestSize.Level1)
{
    uint32_t value = 0;
    EXPECT_EQ(OH_HiDebug_GetGraphicsMemory(&value), HIDEBUG_SUCCESS);
    EXPECT_EQ(OH_HiDebug_GetGraphicsMemory(nullptr), HIDEBUG_INVALID_ARGUMENT);
    EXPECT_GE(value, 0);
}

/**
 * @tc.name: OH_HiDebug_GetGraphicsMemorySummary
 * @tc.desc: test OH_HiDebug_GetGraphicsMemorySummary. get graphics memorySummary.
 * @tc.type: FUNC
 */
HWTEST_F(HidebugTest, OH_HiDebug_GetGraphicsMemorySummary, TestSize.Level1)
{
    constexpr uint32_t testIntervalNormal = 300;
    EXPECT_EQ(OH_HiDebug_GetGraphicsMemorySummary(testIntervalNormal, nullptr), HIDEBUG_INVALID_ARGUMENT);
    HiDebug_GraphicsMemorySummary summary{};
    EXPECT_EQ(OH_HiDebug_GetGraphicsMemorySummary(testIntervalNormal, &summary), HIDEBUG_SUCCESS);
    constexpr uint32_t testIntervalMin = 1;
    EXPECT_EQ(OH_HiDebug_GetGraphicsMemorySummary(testIntervalMin, &summary), HIDEBUG_SUCCESS);
    constexpr uint32_t testIntervalMax = 3601;
    EXPECT_EQ(OH_HiDebug_GetGraphicsMemorySummary(testIntervalMax, &summary), HIDEBUG_SUCCESS);
    HiDebug_GraphicsMemorySummary tmpSummary = summary;
    constexpr uint32_t testInterval = 5;
    struct timespec req = {0};
    req.tv_sec = 4;
    req.tv_nsec = 800000000; // 睡眠4.8秒
    nanosleep(&req, nullptr);
    EXPECT_EQ(OH_HiDebug_GetGraphicsMemorySummary(testInterval, &summary), HIDEBUG_SUCCESS);
    EXPECT_EQ(summary.gl, tmpSummary.gl);
    EXPECT_EQ(summary.graph, tmpSummary.graph);
    EXPECT_GE(summary.gl, 0);
    EXPECT_GE(summary.graph, 0);
}

/**
 * @tc.name: OH_HiDebug_SetMallocDispatchTable
 * @tc.desc: test OH_HiDebug_SetMallocDispatchTable. set malloc dispatch table.
 * @tc.type: FUNC
 */
HWTEST_F(HidebugTest, OH_HiDebug_SetMallocDispatchTable, TestSize.Level1)
{
    HiDebug_MallocDispatch* original = (HiDebug_MallocDispatch*)OH_HiDebug_GetDefaultMallocDispatchTable();
    EXPECT_NE(original, nullptr);
    HiDebug_MallocDispatch* current = (HiDebug_MallocDispatch*)original->malloc(sizeof(HiDebug_MallocDispatch));
    EXPECT_NE(current, nullptr);
    memset_s(current, sizeof(HiDebug_MallocDispatch), 0, sizeof(HiDebug_MallocDispatch));
    current->malloc = TestCustomMalloc;
    current->free = TestCustomFree;
    EXPECT_EQ(OH_HiDebug_SetMallocDispatchTable(nullptr), HIDEBUG_INVALID_ARGUMENT);
    EXPECT_EQ(OH_HiDebug_SetMallocDispatchTable(current), HIDEBUG_SUCCESS);
    int* temp = (int*)malloc(sizeof(int));
    *temp = 8;
    int* temp2 = (int*)malloc(sizeof(int));
    *temp2 = 10;
    int ret = *temp2 + *temp;
    EXPECT_EQ(ret, 18);
    free(temp);
    free(temp2);
    DesCustomMalloc(current);
}

/**
 * @tc.name: OH_HiDebug_SetMallocDispatchTableMmap test mmap and munmap
 * @tc.desc: test OH_HiDebug_SetMallocDispatchTable. set malloc dispatch table.
 * @tc.type: FUNC
 */
HWTEST_F(HidebugTest, OH_HiDebug_SetMallocDispatchTableMmap, TestSize.Level1)
{
    HiDebug_MallocDispatch* original = (HiDebug_MallocDispatch*)OH_HiDebug_GetDefaultMallocDispatchTable();
    EXPECT_NE(original, nullptr);
    HiDebug_MallocDispatch* current = (HiDebug_MallocDispatch*)original->malloc(sizeof(HiDebug_MallocDispatch));
    EXPECT_NE(current, nullptr);
    memset_s(current, sizeof(HiDebug_MallocDispatch), 0, sizeof(HiDebug_MallocDispatch));
    current->mmap = TestCustomMmap;
    current->munmap = TestCustomMunmap;
    EXPECT_EQ(OH_HiDebug_SetMallocDispatchTable(current), HIDEBUG_SUCCESS);
    char* tempMap = nullptr;
    tempMap = (char*)mmap(NULL, 100, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    EXPECT_NE(tempMap, nullptr);
    if (sprintf_s(tempMap, 100, "%s", "hi, this is test mmap") > 0) {
        EXPECT_EQ(std::string(tempMap), "hi, this is test mmap");
    }
    munmap(tempMap, 100);
    DesCustomMalloc(current);
}

/**
 * @tc.name: OH_HiDebug_SetMallocDispatchTableMmap test mmap and munmap
 * @tc.desc: test OH_HiDebug_SetMallocDispatchTable. set malloc dispatch table.
 * @tc.type: FUNC
 */
HWTEST_F(HidebugTest, OH_HiDebug_SetMallocDispatchTableAlloc, TestSize.Level1)
{
    HiDebug_MallocDispatch* original = (HiDebug_MallocDispatch*)OH_HiDebug_GetDefaultMallocDispatchTable();
    EXPECT_NE(original, nullptr);
    HiDebug_MallocDispatch* current = (HiDebug_MallocDispatch*)original->malloc(sizeof(HiDebug_MallocDispatch));
    EXPECT_NE(current, nullptr);
    memset_s(current, sizeof(HiDebug_MallocDispatch), 0, sizeof(HiDebug_MallocDispatch));
    current->calloc = TestCustomCalloc;
    current->realloc = TestCustomRealloc;
    EXPECT_EQ(OH_HiDebug_SetMallocDispatchTable(current), HIDEBUG_SUCCESS);
    int* tempCalloc = (int*)calloc(10, sizeof(int));
    EXPECT_NE(tempCalloc, nullptr);
    *tempCalloc = 10;
    EXPECT_EQ(*tempCalloc, 10);
    int* temp = (int*)realloc(tempCalloc, 20 * sizeof(int));
    EXPECT_NE(temp, nullptr);
    *temp = 20;
    EXPECT_EQ(*temp, 20);
    free(temp);
    DesCustomMalloc(current);
}

/**
 * @tc.name: OH_HiDebug_RequestThreadLiteSampling test.
 * @tc.desc: test OH_HiDebug_RequestThreadLiteSampling.
 * @tc.type: FUNC
 */
HWTEST_F(HidebugTest, OH_HiDebug_RequestThreadLiteSamplingTest, TestSize.Level0)
{
    auto callback = [] (const char* stacks) {};
    ASSERT_EQ(OH_HiDebug_RequestThreadLiteSampling(nullptr, callback), HIDEBUG_INVALID_ARGUMENT);
    HiDebug_ProcessSamplerConfig config;
    ASSERT_EQ(OH_HiDebug_RequestThreadLiteSampling(&config, nullptr), HIDEBUG_INVALID_ARGUMENT);
    config.duration = 999;
    ASSERT_EQ(OH_HiDebug_RequestThreadLiteSampling(&config, callback), HIDEBUG_INVALID_ARGUMENT);
    config.tids = nullptr;
    ASSERT_EQ(OH_HiDebug_RequestThreadLiteSampling(&config, callback), HIDEBUG_INVALID_ARGUMENT);
}

/**
 * @tc.name: OH_HiDebug_RequestThreadLiteSampling test.
 * @tc.desc: test OH_HiDebug_RequestThreadLiteSampling.
 * @tc.type: FUNC
 */
HWTEST_F(HidebugTest, OH_HiDebug_RequestThreadLiteSamplingTest2, TestSize.Level1)
{
    auto callback = [] (const char* stacks) {
        ASSERT_NE(stacks, nullptr);
        ASSERT_GT(std::strlen(stacks), 0);
    };
    uint32_t tid = static_cast<uint32_t>(gettid());
    HiDebug_ProcessSamplerConfig config{
        .tids = &tid,
        .size = 1,
        .duration = 1500,
        .frequency = 0,
        .reserved = 0
    };
    std::thread([&] {
        ASSERT_EQ(OH_HiDebug_RequestThreadLiteSampling(&config, callback), HIDEBUG_SUCCESS);
    }).detach();
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::thread([&] {
        ASSERT_EQ(OH_HiDebug_RequestThreadLiteSampling(&config, callback), HIDEBUG_UNDER_SAMPLING);
    }).detach();
    std::this_thread::sleep_for(std::chrono::seconds(1));
}
} // namespace
